package main

import (
	"bufio"
	"bytes"
	"container/list"
	"database/sql"
	"fmt"
	_ "github.com/lib/pq"
	"gopkg.in/ini.v1"
	"html/template"
	"io"
	"log"
	"os"
	"os/exec"
	"reflect"
	"strings"
	"sync"
)

const DRIVER_NAME = "postgres"

const CRATE_PARTITION_TMPLATE = "\n PARTITION %s VALUES LESS THAN (%s) TABLESPACE pg_default"

type AppConfig struct {
	ExportDbHost     string `ini:"export.db.host"`
	ExportDbPort     string `ini:"export.db.port"`
	ExportDbUser     string `ini:"export.db.user"`
	ExportDbPassword string `ini:"export.db.password"`
	ExportDbUrl      string `ini:"export.db.url"`

	ExportDbschemaTemplate  string `ini:"export.dbinit.template"`
	ExportDbDataTemplate    string `ini:"export.dbdata.template"`
	ExportHeritsTemplate    string `ini:"export.herits.template"`
	ExportDbdataFenquFlag   string `ini:"export.dbdatafenqu.flag"`
	ExportDbdataFlag        string `ini:"export.dbdata.flag"`
	ExportDbdataReplaceChar string `ini:"export.dbdata.repacleChar"`
	SqlExportDatabase       string `ini:"sql.export.database"`
	CheckIndex              string `ini:"check.index"`
	CheckKeyword            string `ini:"check.keyword"`
	CheckCloumnType         string `ini:"check.cloumntype"`
	CheckPartitionTables    string `ini:"check.partitiontables"`

	ImportDbHost           string `ini:"import.db.host"`
	ImportDbPort           string `ini:"import.db.port"`
	ImportDbUser           string `ini:"import.db.user"`
	ImportDbPassword       string `ini:"import.db.password"`
	ImportDbinitFlag       string `ini:"import.dbinit.flag"`
	ImportDbdataFlag       string `ini:"import.dbdata.flag"`
	ImportDbUrl            string `ini:"import.db.url"`
	ImportCreatedbTemplate string `ini:"import.createdb.template"`
	ImportDbinitTemplate   string `ini:"import.dbinit.template"`
	ImportDbdataTemplate   string `ini:"import.dbdata.template"`
}

type PgIndexes struct {
	Tablename string
	Indexname string
	Indexdef  string
	DbName    string
}

type PgInformationSchema struct {
	TableCatalog string
	TableSchema  string
	TableName    string
	ColumnName   string
	DataType     string
	UdtName      string
}

type PgInherits struct {
	HeritsTableName string
	TableName       string
	DbName          string
}

type HeritsInfo struct {
	Name       string
	Start      string
	End        string
	HeritsName string
}

func main() {
	//记录日志
	f, err := os.OpenFile("main.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
	if err != nil {
		return
	}
	defer func() {
		f.Close()
	}()

	// 组合一下即可，os.Stdout代表标准输出流
	multiWriter := io.MultiWriter(os.Stdout, f)
	log.SetOutput(multiWriter)
	log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)

	config, err := ini.Load("config.ini")
	checkErr(err)
	//读取并映射到结构体中
	var appConfig = new(AppConfig)
	err = config.MapTo(appConfig)
	if err != nil {
		fmt.Printf("映射配置信息失败 %v", err)
		return
	}
	fillParamer(appConfig)

	// Connect to database
	db, err := sql.Open(DRIVER_NAME, strings.ReplaceAll(appConfig.ExportDbUrl, "%s", DRIVER_NAME))
	if err != nil {
		log.Fatal(err)
	}

	log.Println("The database is connected")

	dbList := QueryExportDatabase(db, appConfig)
	dbPartitionMap := make(map[string]*list.List)
	for i := dbList.Front(); i != nil; i = i.Next() {
		if i != nil {
			//最后一个/和？之间替换
			db_name := i.Value.(string)
			db, err := sql.Open(DRIVER_NAME, strings.ReplaceAll(appConfig.ExportDbUrl, "%s", db_name))
			if err != nil {
				log.Fatal(err)
			}
			log.Printf("The database %s is connected", db_name)
			QueryCheckIndex(db, appConfig, db_name)
			QueryCheckKeyword(db, appConfig)
			QueryCheckCloumnType(db, appConfig)
			partitionList := QueryCheckPartitionTables(db, appConfig, db_name)
			dbPartitionMap[db_name] = partitionList
		}
	}

	//数据库#分区表作为key，分区表为名称
	partitionTableMap := make(map[string]string)

	log.Println("所有的检查执行完成,准备导出数据库")
	for i := dbList.Front(); i != nil; i = i.Next() {
		db_name := i.Value.(string)
		split_db := "__"

		partitionList := dbPartitionMap[db_name]
		//去掉重复的分区表
		for j := partitionList.Front(); j != nil; j = j.Next() {
			pgInherits := j.Value.(*PgInherits)
			heritsTableName := pgInherits.HeritsTableName
			partitionTableMap[pgInherits.DbName+split_db+heritsTableName] = heritsTableName
		}

		//便利分区表，然后逐个导出
		for k, v := range partitionTableMap {
			heritsTableName := v
			var strExportDbschemaTemplate = fmt.Sprintf(appConfig.ExportDbschemaTemplate, db_name, " -t "+v, k)
			Command(strExportDbschemaTemplate)
			log.Printf("导出表:%s结构完成", heritsTableName)
			Command(fmt.Sprintf("%s  %s.sql", appConfig.ExportDbdataReplaceChar, k))
			log.Println("替换里面的pulbic.完成")
		}

		var exportAllTabs string
		for j := partitionList.Front(); j != nil; j = j.Next() {
			pgInherits := j.Value.(*PgInherits)
			exportAllTabs += " -T " + pgInherits.HeritsTableName
			exportAllTabs += " -T " + pgInherits.TableName
		}
		var strExportDbschemaAllTemplate = fmt.Sprintf(appConfig.ExportDbschemaTemplate, db_name, exportAllTabs, db_name+"_all")
		log.Println(strExportDbschemaAllTemplate)
		Command(strExportDbschemaAllTemplate)
		log.Println("导出全表结构完成")
		Command(fmt.Sprintf("%s  %s_all.sql", appConfig.ExportDbdataReplaceChar, db_name))

		log.Println("替换里面的pulbic.完成")

		// 遍历 Map
		for k, v := range partitionTableMap {
			fmt.Printf("key=%s, value=%s\n", k, v)
			var val = strings.Split(k, split_db)
			//查看分区信息,ini配置文件含有'>'拼装有问题，所以在这里拼接
			Command(fmt.Sprintf(appConfig.ExportHeritsTemplate, val[0], val[1], "> "+k+".log"))
			//解析分区信息
			var heritsInfoArr = ReadHeritsWrapper(fmt.Sprintf("%s.log", k))
			WriteHeritsWrapper(k+".sql", heritsInfoArr, fmt.Sprintf("%s_all.sql", db_name))
			//清理临时文件如：iho_emr__vao1.sql
			CleanTempFile(k + ".sql")
			CleanTempFile(k + ".log")
		}

		//写入分区表
		if appConfig.ExportDbdataFlag == "1" {
			if appConfig.ExportDbdataFenquFlag == "0" {
				Command(fmt.Sprintf(appConfig.ExportDbDataTemplate, db_name, db_name))
			} else {
				//只导入分区数据，为了补充数据
				var exportAllTabs string
				for j := partitionList.Front(); j != nil; j = j.Next() {
					pgInherits := j.Value.(*PgInherits)
					exportAllTabs += " -t " + pgInherits.HeritsTableName
					exportAllTabs += " -t " + pgInherits.TableName
				}
				if exportAllTabs == "" {
					Command(fmt.Sprintf(appConfig.ExportDbDataTemplate, db_name, db_name))
				} else {
					Command(fmt.Sprintf(appConfig.ExportDbDataTemplate, db_name+exportAllTabs, db_name))
				}
			}
			log.Println("导出数据")
			Command(fmt.Sprintf("%s  %s_data.sql", appConfig.ExportDbdataReplaceChar, db_name))
			log.Println("替换里面的pulbic.完成")
			var copyStr = ""
			// 遍历 Map
			var m = 0
			for j := partitionList.Front(); j != nil; j = j.Next() {
				pgInherits := j.Value.(*PgInherits)
				copyStr += fmt.Sprintf("s/COPY %s (/COPY %s (", pgInherits.TableName, pgInherits.HeritsTableName)
				if m == partitionList.Len()-1 {
					copyStr += "/g"
				} else {
					copyStr += "/;"
				}
				m++

			}
			copyStr = "sed -i '" + copyStr + "'"
			Command(fmt.Sprintf("%s  %s_data.sql", copyStr, db_name))
			log.Println("替换里面的分区表完成")
		}

		if appConfig.ImportDbinitFlag == "1" {
			Command(fmt.Sprintf(appConfig.ImportCreatedbTemplate, db_name, db_name))
			log.Println("准备导入表结构")
			Command(fmt.Sprintf(appConfig.ImportDbinitTemplate, db_name, db_name, db_name))
			log.Println("导入表结构完成")
		}

		if appConfig.ImportDbdataFlag == "1" {
			log.Println("准备导入数据")
			Command(fmt.Sprintf(appConfig.ImportDbdataTemplate, db_name, db_name, db_name))
			log.Println("导入数据完成")
		}
		log.Printf("数据库：%s,任务执行完成", db_name)
	}
	log.Println("所有的任务执行完成")
}

func CleanTempFile(fileName string) {
	err := os.Remove(fileName)
	if err != nil {
		// 删除失败
		log.Printf("删除文件名：%s失败，失败原因：%v", fileName, err)

	} else {
		// 删除成功
		log.Printf("删除文件名：:%s成功", fileName)
	}

}

func fillParamer(config *AppConfig) {
	v := reflect.ValueOf(config)
	appConfigType := reflect.TypeOf(AppConfig{})

	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}
	t := v.Type()

	for i := 0; i < t.NumField(); i++ {
		value := v.Field(i).Interface()
		tmpl, err := template.New("").Parse(fmt.Sprintf("%v", value))
		if err != nil {
			panic(err)
		}
		var tmplBytes bytes.Buffer
		err = tmpl.Execute(&tmplBytes, config)
		if err != nil {
			panic(err)
		}
		value = tmplBytes.String()
		fmt.Printf("%s\n", value)
		setFieldValue(config, appConfigType.Field(i).Name, value)
	}
}

func WriteHeritsWrapper(file string, arr *list.List, out string) {
	newContent, _ := ReadPartition(file, arr)
	WriteAppendTxt(out, fmt.Sprintf(strings.Join(newContent, "\r\n")))
}

// 处理分区表语法格式转换
func ReadHeritsWrapper(file string) *list.List {
	heritStr, _ := ReadHerits(file)
	var heritsArr = list.New()
	if heritStr != nil {
		for i := 0; i < len(heritStr); i++ {
			content := heritStr[i]
			if content != "" && !strings.Contains(content, "FOR VALUES WITH ") {
				heritStr := new(HeritsInfo)
				heritStr.Name = strings.Split(content, "FOR VALUES")[0]
				heritStr.Start = strings.Split(strings.Split(content, "FROM (")[1], ") TO")[0]
				heritStr.End = strings.Split(strings.Split(content, "TO (")[1], ")")[0]
				log.Printf("识别到分区%+v", heritStr)
				heritsArr.PushBack(heritStr)
			} else if content != "" && strings.Contains(content, "FOR VALUES WITH ") {
				log.Printf("warn 存在hash分区，需要单独处理，目前未处理,file:%s", file)
				log.Printf("没有读取到分区%+v", heritStr)
			}
		}
	}
	return heritsArr
}

// 读取分区
func ReadHerits(file string) ([]string, error) {
	flag := false
	f, err := os.Open(file)
	if err != nil {
		fmt.Printf("read fail,%s", file)
	}

	//把file读取到缓冲区中
	defer f.Close()
	reader := bufio.NewReader(f)

	l := make([]string, 0, 64)
	// 按行读取
	for {
		line, _, err := reader.ReadLine()
		if err != nil {
			if err == io.EOF {
				break
			} else {
				return nil, err
			}
		}

		var content = string(line)

		//中文和英文
		substr := "分区: "
		if strings.Contains(content, substr) {
			content = strings.ReplaceAll(content, substr, "")
			flag = true
		}
		substr = "Partitions:"
		if strings.Contains(content, substr) {
			content = strings.ReplaceAll(content, substr, "")
			flag = true
		}

		if flag {
			l = append(l, content)
		}
	}
	log.Println("读取分区信息完成")
	return l, nil

}

// QueryCheckIndex /*
func QueryCheckIndex(db *sql.DB, config *AppConfig, db_name string) *list.List {
	checkIndexList := list.New()
	rows, _ := db.Query(config.CheckIndex)
	defer rows.Close()
	for rows.Next() {
		var pgIndexes = new(PgIndexes)
		pgIndexes.DbName = db_name
		_ = rows.Scan(&pgIndexes.Tablename, &pgIndexes.Indexname, &pgIndexes.Indexdef)
		log.Printf("数据库：%s,\t表名:%s\t,索引名:%s不符合CheckIndex规则\t,索引详情:%s", pgIndexes.DbName, pgIndexes.Tablename, pgIndexes.Indexname, pgIndexes.Indexdef)
		checkIndexList.PushBack(pgIndexes)

	}
	return checkIndexList
}

// QueryCheckKeyword /*
func QueryCheckKeyword(db *sql.DB, config *AppConfig) *list.List {
	checkIndexList := list.New()
	rows, _ := db.Query(config.CheckKeyword)
	defer rows.Close()
	for rows.Next() {
		log.Println("The QueryCheckKeyword rows netxt")
		var pgInformationSchema = new(PgInformationSchema)
		_ = rows.Scan(&pgInformationSchema.TableCatalog, &pgInformationSchema.TableSchema, &pgInformationSchema.TableName, &pgInformationSchema.ColumnName, &pgInformationSchema.DataType, &pgInformationSchema.UdtName)
		log.Printf("数据库:%s \t,表名:%s，不符合Keyword规范", pgInformationSchema.TableCatalog, pgInformationSchema.TableName)
		checkIndexList.PushBack(pgInformationSchema)
	}
	return checkIndexList
}

// QueryCheckKeyword /*
func QueryCheckCloumnType(db *sql.DB, config *AppConfig) *list.List {
	checkIndexList := list.New()
	rows, _ := db.Query(config.CheckCloumnType)
	defer rows.Close()
	for rows.Next() {
		var pgInformationSchema = new(PgInformationSchema)
		_ = rows.Scan(&pgInformationSchema.TableCatalog, &pgInformationSchema.TableSchema, &pgInformationSchema.TableName, &pgInformationSchema.ColumnName, &pgInformationSchema.DataType, &pgInformationSchema.UdtName)
		log.Printf("数据库:%s\t,表名:%s\t字段名:%s，字段类型:%s\t,不符合CloumnType规范", pgInformationSchema.TableCatalog, pgInformationSchema.TableName, pgInformationSchema.ColumnName, pgInformationSchema.DataType)
		checkIndexList.PushBack(pgInformationSchema)

	}
	return checkIndexList
}

// QueryCheckPartitionTables /*
func QueryCheckPartitionTables(db *sql.DB, config *AppConfig, db_name string) *list.List {
	checkIndexList := list.New()
	rows, _ := db.Query(config.CheckPartitionTables)
	defer rows.Close()
	for rows.Next() {
		var pgInherits = new(PgInherits)
		pgInherits.DbName = db_name
		_ = rows.Scan(&pgInherits.HeritsTableName, &pgInherits.TableName)
		log.Printf("数据库：%s\t,分区表:%s,\t 表名:%s，需要特殊处理", pgInherits.DbName, pgInherits.HeritsTableName, pgInherits.TableName)
		checkIndexList.PushBack(pgInherits)
	}
	return checkIndexList
}

// QueryExportDatabase /*
func QueryExportDatabase(db *sql.DB, config *AppConfig) *list.List {
	dbList := list.New()
	rows, _ := db.Query(config.SqlExportDatabase)
	defer rows.Close()
	for rows.Next() {
		var datname string
		_ = rows.Scan(&datname)
		dbList.PushBack(datname)
		log.Printf("databse_name:%s ", datname)
	}
	return dbList
}

// 阻塞式的执行外部shell命令的函数,等待执行完毕并返回标准输出
func exec_shell(s string) (string, error) {
	//函数返回一个*Cmd，用于使用给出的参数执行name指定的程序
	cmd := exec.Command("/bin/bash", "-c", s)
	//读取io.Writer类型的cmd.Stdout，再通过bytes.Buffer(缓冲byte类型的缓冲器)将byte类型转化为string类型(out.String():这是bytes类型提供的接口)
	var out bytes.Buffer
	cmd.Stdout = &out
	//Run执行c包含的命令，并阻塞直到完成。  这里stdout被取出，cmd.Wait()无法正确获取stdin,stdout,stderr，则阻塞在那了
	err := cmd.Run()
	checkErr(err)
	return out.String(), err
}

// 错误处理函数
func checkErr(err error) {
	if err != nil {
		fmt.Println(err)
		panic(err)
	}
}

func Command(cmd string) error {
	log.Println("cmd:" + cmd)
	//c := exec.Command("cmd", "/C", cmd) 	// windows
	c := exec.Command("bash", "-c", cmd) // mac or linux
	stdout, err := c.StdoutPipe()
	if err != nil {
		return err
	}
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		reader := bufio.NewReader(stdout)
		for {
			readString, err := reader.ReadString('\n')
			if err != nil || err == io.EOF {
				return
			}
			fmt.Print(readString)
		}
	}()
	err = c.Start()
	wg.Wait()
	return err
}

func ReadTxt(file string) ([]string, error) {
	f, err := os.Open(file)
	if err != nil {
		fmt.Println("read fail")
	}

	//把file读取到缓冲区中
	defer f.Close()
	reader := bufio.NewReader(f)

	l := make([]string, 0, 64)
	log.Println("按行读取")
	// 按行读取
	for {
		line, _, err := reader.ReadLine()
		if err != nil {
			if err == io.EOF {
				break
			} else {
				return nil, err
			}
		}

		content := string(line)

		//ALTER INDEX index_cbm1_cbm01 ATTACH PARTITION cbm1_202301_cbm01_idx;
		if strings.Contains(content, "ALTER INDEX ") && strings.Contains(content, "ATTACH PARTITION") {
			continue
		}

		/**
		ALTER TABLE ONLY cbm1 ATTACH PARTITION cbm1_202301 FOR VALUES FROM ('2023-01-01 00:00:00') TO ('2023-07-01 00:00:00');
		==>
		ALTER TABLE cmb1 ADD PARTITION cbm1_202301  END ('2023-07-01 00:00:00')  **/
		//if strings.Contains(content, "ALTER TABLE ONLY") && strings.Contains(content, "ATTACH PARTITION") {
		//	tab := strings.Split(strings.Split(content, "ATTACH PARTITION")[0], "ALTER TABLE ONLY")[1]
		//	subTab := strings.Split(strings.Split(content, "ATTACH PARTITION")[1], "FOR VALUES FROM")[0]
		//	startRange := strings.Split(strings.Split(content, "FROM (")[1], ") TO")[0]
		//	endRange := strings.Split(strings.Split(content, "TO (")[1], ");")[0]
		//	log.Printf("tab:%s,subTab:%s，startRange:%s,endRange:%s", tab, subTab, startRange, endRange)
		//	content = fmt.Sprintf(PARTITION_TMPLATE, tab, subTab, startRange, endRange)
		//} else {
		if strings.Contains(content, "ALTER TABLE ONLY ") {
			content = strings.ReplaceAll(content, "ALTER TABLE ONLY ", "ALTER TABLE ")
		}
		//}

		l = append(l, content)
	}
	log.Println("按行读取完成")
	return l, nil
}

func ReadPartition(file string, arrList *list.List) ([]string, error) {
	if arrList.Len() == 0 {
		return nil, nil
	}

	f, err := os.Open(file)
	if err != nil {
		fmt.Printf("read fail,%s", file)
	}

	//把file读取到缓冲区中
	defer f.Close()
	reader := bufio.NewReader(f)

	l := make([]string, 0, 64)
	log.Println("按行读取")
	// 按行读取
	for {
		line, _, err := reader.ReadLine()
		if err != nil {
			if err == io.EOF {
				break
			} else {
				return nil, err
			}
		}

		content := string(line)

		if strings.Contains(content, "PARTITION BY RANGE") {
			content = strings.ReplaceAll(fmt.Sprintf("WITH (orientation=row, compression=no, fillfactor=80)\n%s\n(", content), ";", "")
			var body = ""
			var j = 0
			for i := arrList.Front(); i != nil; i = i.Next() {
				var heritsInfo = i.Value.(*HeritsInfo)
				partInfoStr := fmt.Sprintf(CRATE_PARTITION_TMPLATE, heritsInfo.Name, heritsInfo.End)
				if j == arrList.Len()-1 {
					body += partInfoStr
				} else {
					body += partInfoStr + ","
				}
				j++
			}
			body += "\n)\nENABLE ROW MOVEMENT;"
			content += body
			log.Println("内容：" + content)
		}
		l = append(l, content)
	}
	log.Println("按行读取完成")
	return l, nil
}

func WriteTxt(file string, content string) {
	var err error
	if err = os.WriteFile(file, []byte(content), 0666); err != nil {
		fmt.Println("Writefile Error =", err)
	}
}

func WriteAppendTxt(filePath string, content string) {
	file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		fmt.Println("文件打开失败", err)
	}
	//及时关闭file句柄
	defer file.Close()
	//写入文件时，使用带缓存的 *Writer
	write := bufio.NewWriter(file)
	write.Write([]byte(content))
	//Flush将缓存的文件真正写入到文件中
	write.Flush()
}

func setFieldValue(obj interface{}, name string, value interface{}) {
	reflect.ValueOf(obj).Elem().FieldByName(name).Set(reflect.ValueOf(value))
}
